
import Config.ConfigReader;
import DatabaseManager.DatabaseManager;
import Server.Client;
import Server.EventReceiver;
import Server.ClientListener;
import java.net.Socket;
import SystemDataTypes.*;
import iLogger.iLogger;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.json.JSONObject;

import date.DateParser;

/**
 * Event Catcher recibe los eventos de modificacion de información
 * @author Administrador
 */

public class EventCatcher implements EventReceiver{

    // updateListener escucha si es necesario calcular todos los estados de vuelos

    private ClientListener listener = null; 
    Calculator calculator; 
    private int id;
    static final String states[]={"Estado 1", "Estado 2", "Estado 3"};
    public String localAirportId;
    static int listeningPort = 3000;
    static String listeningHost = "localhost";
    static int coordinatorPort = 3005;
    static int coordinatorReportPort = 3006;
    
    static String subscriptionManagerHost = "localhost";
    static int subscriptionManagerPort = 1250;
    

    String coordinatorHost = "localhost";
  
    /**
     * 
     * @param calculator el calculador con el que se conecta
     */
    public EventCatcher(Calculator calculator)
    {
        this.calculator = calculator;
        try {
            // vemos en qué puerto escucha el listener
            listeningHost = ConfigReader.getString("EventCatcherlisteningHost");
            listeningPort = ConfigReader.getInt("EventCatcherlisteningPort");
            coordinatorPort = ConfigReader.getInt("coordinatorAuthorizationPort");
            coordinatorReportPort = ConfigReader.getInt("coordinatorReportPort");
            coordinatorHost = ConfigReader.getString("coordinatorHost");
            subscriptionManagerHost = ConfigReader.getString("subscriptionManagerHost");
            subscriptionManagerPort = ConfigReader.getInt("subscriptionManagerPort");      
            localAirportId = ConfigReader.getString("airportId");
        } catch (Exception ex) {
           System.out.println(ex.getMessage());
        }
            
        // creamos el listener        
        try {
            listener = new ClientListener(listeningPort, this);
        } catch (IOException ex) {
            Logger.getLogger(EventCatcher.class.getName()).log(Level.SEVERE, null, ex);
        }
        // hacemos que empiece a escuchar
        listener.start();
        
        this.id = listener.getLocalPort();
        
        // hacemos que se suscriba al bus
        subscribeToEventBus();
        
    }
    
    public void receiveMessage(String strMessage, Socket socket) {  
        
        // respondemos al cliente con un ack de recepcion
        PrintStream os = null;

        try {
            os = new PrintStream(socket.getOutputStream());
        } catch (IOException ex) {
          iLogger.log(ex.getMessage());
        }    
        
        os.println("OK");             
        os.close();
        
        // si es un mensaje de datos
        if(socket.getLocalPort()==listeningPort){
            processDataEvent(strMessage);
        }
    }

    private boolean askForAuthorization(Date date) {

        Client client = new Client(coordinatorHost, coordinatorPort);

        String response = null;                        
        try {
            // armamos el pedido de autorizacion
            JSONObject obj = new JSONObject();
            obj.put("EventCatcher", this.id);
            obj.put("nOrder", DateParser.toString(date));
            // lo mandamos y recibimos la respuesta
            response = client.sendMessage(obj.toString());
        } catch (Exception ex) {
            Logger.getLogger(EventCatcher.class.getName()).log(Level.SEVERE, null, ex);
        }
      
        System.out.println(response);
        if(response.equals("OK")) return true; else return false;
    }
    
    private void processDataEvent(String strMessage) {
        
         // Ahora podemos procesar el pedido

        DatabaseManager connection = new DatabaseManager();

        boolean forward = true;
        boolean enviable = false;
        System.out.println("EC: Lleg� el mensaje: " + strMessage);
        
        Object object = EventReader.readFromJSONString(strMessage);      
        
        // si no era de un tipo reconocido, se descarta
        if (object == null) return;
        
        if(object instanceof String)
        {
            String str = (String)object;
            if(str.equals("RefreshTerminals")){
                System.out.println("Mensaje de reinicio recibido.");
                calculator.calculateAll();
                return;
            }
        }

        // Aquí debe comunicarse con el Coordinator
        // para saber si puede seguir y manejar este evento
        if(!askForAuthorization(EventReader.readOrderFromJSONString(strMessage))) 
        {
            System.out.println("No autorizado a tratar evento " + EventReader.readOrderFromJSONString(strMessage));
            return;
        }
            System.out.println("Autorizado a tratar evento " + EventReader.readOrderFromJSONString(strMessage));

        int flightId;    
        
        if(object instanceof FlightPlan)
        {
        	System.out.println("es un FP!");
            FlightPlan fp = (FlightPlan) object;
        	System.out.println("Lo inserto en la base");
            connection.insert(fp);              
            flightId = fp.flightId;
            
            // mandamos al Subscription Manager el flight plan
            sendFlightPlanToSubscriptionManager(fp);            
        } 
        else if(object instanceof FlightData)
        {
            FlightData fd = (FlightData) object;            
            flightId = fd.flightId;

            FlightPlan fplan = connection.retrieveFlightPlan(flightId);
            if(fplan==null){
                System.out.println("flight data without flight plan");
                connection.insertExternalFlightData(fd);
                reportState(2);
                return;
            }
            connection.insert(fd, EventReader.readOrderFromJSONString(strMessage));         
        } else if(object instanceof TowerEvent)
        {
        	System.out.println("Recibí un TowerEvent");       
        	TowerEvent te = (TowerEvent) object;
        	flightId = te.flightId;
        	enviable=true;
        	if(!te.idAeropuerto.equals(localAirportId)) forward = false;
        	connection.insertTowerEvent(te, EventReader.readOrderFromJSONString(strMessage));
            reportState(2);        	
        }
        else
        {
            throw new UnsupportedOperationException("Tipo de dato no implementado");
        }      

        reportState(1);
        calculator.calculate(flightId, forward, enviable);               
        reportState(2);
   }

    private void reportState(int i) {
        
        Client stateSender = null;

        // creamos el reportador de estados
        stateSender = new Client(coordinatorHost, coordinatorReportPort);
        
        JSONObject msg = new JSONObject();
        
        try {            
            msg.put("id", this.id);            
            msg.put("state", states[i]);            
            stateSender.sendNoResponseMessage(msg.toString());            
        } catch (Exception ex) {
            Logger.getLogger(EventCatcher.class.getName()).log(Level.SEVERE, null, ex);
            }      
        
        stateSender.close();
    }
    
    private static void subscribeToEventBus() {
    	
        int subscriptioBusSubscriptionPort = 4000;
        String subscriptionBusHost = "localhost";
        System.out.println("Subscribiendose al bus de la izquierda");
        try {
			subscriptioBusSubscriptionPort = ConfigReader.getInt("subscriptionBusSubscriptionPort");
	        subscriptionBusHost = ConfigReader.getString("subscriptionBusHost");
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

        Client c = new Client(subscriptionBusHost, subscriptioBusSubscriptionPort);        
        JSONObject petition = new JSONObject();
        try {
            petition.put("url", listeningHost);
            petition.put("port", listeningPort);
            c.sendNoResponseMessage(petition.toString());
        } catch (Exception ex) {
            // Logger.getLogger(EventCatcher.class.getName()).log(Level.SEVERE, null, ex);
        	System.out.println("Unable to subscribe to Bus at " + subscriptionBusHost + ":" + subscriptioBusSubscriptionPort );
        }        
    }
    
    void sendFlightPlanToSubscriptionManager(FlightPlan fp)
    {
    	System.out.println("Sending FLight Plan to SubscriptionManager for Flight " + fp.flightId);
        Client sender = new Client(subscriptionManagerHost, subscriptionManagerPort);
        //TODO: Mandar datos al subscription manager
        // armamos un mensaje de subscripcion al vuelo deseado

	    try {
	        sender.sendNoResponseMessage(fp.toString());
	    } catch (Exception ex) {
	        // Logger.getLogger(EventCatcher.class.getName()).log(Level.SEVERE, null, ex);
	    	System.out.println("Unable to subscribe to Minga2 at " + subscriptionManagerHost + ":" + subscriptionManagerPort );
	    }
    }
        
    
}

